Subsystem 2b: rendered live map in a per-server #map channel (ImageSharp base tile + grid + cargo/heli/chinook markers) - #15
Merged
Conversation
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap DeletePriorBotMessagesAsync in its own try/catch to prevent delete failures (e.g. missing Manage Messages permission) from aborting the repost. OperationCanceledException is rethrown for clean shutdown. Add LogDeleteFailed warning logger and document RecentMessageScan constant. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ct-clear Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new “Subsystem 2b” map-render slice that provisions a per-server #map channel and renders a live map PNG (base tile + grid + cargo/heli/chinook markers) using ImageSharp, wiring it into the host via DI and a new map-image query seam.
Changes:
- Introduces
RustPlusBot.Features.Mapwith rendering/composition/posting + a hosted service that refreshes map posts on events (throttled). - Extends workspace provisioning/locating to support a per-server
#mapchannel (spec, localization, locator + tests). - Adds
IRustServerQuery.GetMapImageAsync/ connection implementations and comprehensive unit tests for the new map feature.
Reviewed changes
Copilot reviewed 41 out of 42 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/RustPlusBot.Features.Workspace.Tests/Locating/MapChannelLocatorTests.cs | Adds tests for locating provisioned #map channels with TTL cache behavior. |
| tests/RustPlusBot.Features.Map.Tests/RustPlusBot.Features.Map.Tests.csproj | New test project for the map feature. |
| tests/RustPlusBot.Features.Map.Tests/WorldToPixelTests.cs | Verifies world→pixel projection math. |
| tests/RustPlusBot.Features.Map.Tests/MarkerGlyphsTests.cs | Tests glyph mapping for known/unknown marker kinds. |
| tests/RustPlusBot.Features.Map.Tests/MapRendererTests.cs | Tests ImageSharp render output sizing and marker overlay differences. |
| tests/RustPlusBot.Features.Map.Tests/MapRegistrationTests.cs | Verifies DI registrations/singleton lifetimes. |
| tests/RustPlusBot.Features.Map.Tests/MapRefreshThrottleTests.cs | Tests per-server refresh throttling. |
| tests/RustPlusBot.Features.Map.Tests/MapLayerSetTests.cs | Tests the default 2b layer set configuration. |
| tests/RustPlusBot.Features.Map.Tests/MapComposerTests.cs | Tests composition behavior with/without base map bytes. |
| tests/RustPlusBot.Features.Map.Tests/BaseMapCacheTests.cs | Tests base map caching and eviction. |
| tests/RustPlusBot.Features.Connections.Tests/MapImageQueryTests.cs | Adds coverage for the new base-map image query path when connected/not connected. |
| tests/RustPlusBot.Features.Connections.Tests/Fakes/FakeRustSocketSource.cs | Extends fake socket with GetMapImageAsync behavior. |
| src/RustPlusBot.Host/RustPlusBot.Host.csproj | References the new Map feature project. |
| src/RustPlusBot.Host/Program.cs | Registers MapOptions and AddMap() in the host. |
| src/RustPlusBot.Features.Workspace/WorkspaceServiceCollectionExtensions.cs | Registers IMapChannelLocator. |
| src/RustPlusBot.Features.Workspace/WorkspaceKeys.cs | Adds WorkspaceChannelKeys.ServerMap. |
| src/RustPlusBot.Features.Workspace/Specs/ServerWorkspaceSpecProvider.cs | Provisions the per-server #map channel spec. |
| src/RustPlusBot.Features.Workspace/Locating/MapChannelLocator.cs | Adds TTL-cached resolution of provisioned #map channel IDs. |
| src/RustPlusBot.Features.Workspace/Locating/IMapChannelLocator.cs | New interface for resolving #map channel IDs. |
| src/RustPlusBot.Features.Workspace/Localization/LocalizationCatalog.cs | Adds localization keys for the #map channel name. |
| src/RustPlusBot.Features.Map/RustPlusBot.Features.Map.csproj | New Map feature project with ImageSharp + embedded font asset. |
| src/RustPlusBot.Features.Map/Rendering/WorldToPixel.cs | Implements world→pixel projection helper. |
| src/RustPlusBot.Features.Map/Rendering/MarkerPlacement.cs | Value object for projected marker placement. |
| src/RustPlusBot.Features.Map/Rendering/MapRenderer.cs | ImageSharp renderer: base tile resize + grid + marker glyph drawing. |
| src/RustPlusBot.Features.Map/Rendering/MapLayerSet.cs | Layer configuration record with a fixed 2b default. |
| src/RustPlusBot.Features.Map/Posting/IMapChannelPoster.cs | Interface for posting/replacing the map message in Discord. |
| src/RustPlusBot.Features.Map/Posting/DiscordMapChannelPoster.cs | Discord implementation using delete+repost for attachments. |
| src/RustPlusBot.Features.Map/MapServiceCollectionExtensions.cs | DI registration for renderer/cache/composer/poster/hosted service. |
| src/RustPlusBot.Features.Map/MapOptions.cs | Adds configurable refresh interval (default 45s). |
| src/RustPlusBot.Features.Map/Hosting/MapRefreshThrottle.cs | Implements per-(guild,server) refresh gating. |
| src/RustPlusBot.Features.Map/Hosting/MapHostedService.cs | Hosted service that reacts to marker changes and disconnects. |
| src/RustPlusBot.Features.Map/Composing/MapComposer.cs | Composes base map + markers into a rendered PNG. |
| src/RustPlusBot.Features.Map/Composing/BaseMapCache.cs | Caches base JPEG per (guild,server) with disconnect eviction. |
| src/RustPlusBot.Features.Map/Assets/MarkerGlyphs.cs | Defines glyph color/letter mapping for markers. |
| src/RustPlusBot.Features.Connections/Supervisor/ConnectionSupervisor.cs | Adds GetMapImageAsync forwarding to live connection. |
| src/RustPlusBot.Features.Connections/Listening/RustPlusSocketSource.cs | Implements socket GetMapImageAsync via Rust+ API GetMapAsync. |
| src/RustPlusBot.Features.Connections/Listening/IRustServerConnection.cs | Adds GetMapImageAsync to the connection interface. |
| src/RustPlusBot.Abstractions/Connections/IRustServerQuery.cs | Adds GetMapImageAsync seam. |
| RustPlusBot.slnx | Adds new Map projects to the solution. |
| NOTICE | Documents embedded font licensing and notes future icon sourcing. |
| Directory.Packages.props | Adds ImageSharp and ImageSharp.Drawing package versions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Periodic refresh loop: repaint every connected server's #map each MapRefreshInterval so moving cargo/heli/chinook (stable ids → no MapMarkersChangedEvent) track, and post the first image on connect. Marker-change + connect + tick all pass through the existing throttle. - MapComposer reads dimensions from the new IRustServerQuery.GetMapDimensionsAsync seam instead of from a marker, so the grid renders on low-activity/zero-marker servers. - WorldToPixel + MapRenderer grid scale each axis from its own dimension (Width/Height) — correct-by-construction for non-square maps. - MapLayerSet doc: Grid draws lines (labels are 2b-ii), not "lines and labels". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Subsystem 2b — the first of two map-render slices. Ships the rendered live map image in a new per-server
#mapchannel (the live-event layer landed in 2a/2a-ii). No/mapslash command, no map config UI — those and richer layers are deferred to 2b-ii.What this does
#mapchannel (ReadOnly, like#events) holding one bot-managed message: a rendered PNG of the server map.GetMapAsync().JpgImage, cached per connection), draw an A–Z grid + the live cargo ship / patrol helicopter / chinook markers at their current positions, encode a 1024×1024 PNG.Map:MapRefreshInterval(default 45s) per server, coalescing marker bursts. Base-map cache cleared on disconnect.Architecture
New
RustPlusBot.Features.Mapproject (mirrorsFeatures.Events):WorldToPixel(pure coord mapper) ·MapRenderer(ImageSharp → PNG) ·MarkerGlyphs(keyed glyph registry) ·MapLayerSet(explicit layer value object)BaseMapCache(singleton, per-(guild,server) base image) ·MapComposer(cache +IEventStatemarkers → PNG)DiscordMapChannelPoster(delete+repost shim) ·MapHostedService(throttled marker loop + disconnect-clear loop) ·MapRefreshThrottleIRustServerQuery.GetMapImageAsyncseam (base JPEG bytes) · Workspace#mapChannelSpec+IMapChannelLocator·MapOptions(validated)No entity/migration/new gateway intent. Markers ride the existing 2a Rust+ socket state.
Deferred to 2b-ii (recorded for the next slice)
ServerMapSettingsentity + layer-toggle UI · monument icons (skip unknown tokens) · wandering trader (TravellingVendor) · oil-rig activation styling · real icon assets (this slice draws glyphs). Also noted: the base-map cache invalidates only on disconnect, so a mid-session wipe serves a stale tile until reconnect — a natural 2b-ii follow-up.Notes
LiberationSans-Regular.ttf(SIL OFL 1.1) is embedded for map text; seeNOTICE.🤖 Generated with Claude Code